﻿using Modbus.Device;
using NPOI.HSSF.Record.Chart;
using NPOI.SS.Formula.Functions;
using NPOI.XSSF.Streaming.Values;
using Serilog;
using Serilog.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Log = Serilog.Log;

namespace 七天入手上位机第一个项目
{
    public partial class FrmMain : Form
    {
        #region 变量/字段
        //实例化一个Modbus对象
        private NModbusTcp nModbusTcp = new NModbusTcp();
        //定义一个ip变量 之所以要定义成员变量  是因为有不同作用域里会调用到
        private string ip = "127.0.0.1";
        //定义一个port变量
        private int port = 8888;
        //定义一个是否连接PLC成功的变量
        private bool isConected = false;
        //实例化一个读写INI文件的对象
        private IniFile iniFile = new IniFile(Application.StartupPath + "\\Config.INI");
        //判断是否是刚启动窗体，目的是防止更新界面的时候拿到空值
        private bool isRun = true;
        //定义一个取消令牌源 取消令牌源类，拥有Cancel()方法，可以给关联的令牌发送取消信号。
        private CancellationTokenSource cts = new CancellationTokenSource();
        //定义一个键值对 绑定类成员属性和Excel表头
        private Dictionary<string, string> dic = new Dictionary<string, string>();
        //定义一个键值对 用于让变量名和变量对象 成对存在，方便后续的根据名称读写PLC的操作
        private Dictionary<string, Variable> DicVariable = new Dictionary<string, Variable>();
        //定义一个键值对 用于让变量名和变量值成对存在， 用于方便控件获取PLC的最新值
        private Dictionary<string, string> DicVarValue = new Dictionary<string, string>();
        //定义一个键值对 用于工位名称和工位状态绑定成对 方便后续根据名称来刷新状态
        private Dictionary<string, bool> DicStationState = new Dictionary<string, bool>();
        //定义一个是否刷新控件的标志
        private bool isUpdate = false;
        //定义一个变量集合对象  用于存储变量和刷新变量值
        private List<Variable> variables;
        #endregion


        /// <summary>
        /// 无参默认的构造函数
        /// </summary>
        public FrmMain()
        {
            //设计器的初始化内容 勿动  自动生成控件代码
            InitializeComponent();

        }
        /// <summary>
        /// 有参自己新建的一个构造函数
        /// </summary>
        /// <param name="userName"></param>
        public FrmMain(string userName)
        {
            //设计器的初始化内容 勿动  自动生成控件代码
            InitializeComponent();
            this.lbl_UserName.Text = userName;
            //注册一个事件 事件的左上方有一个小闪电图标
            this.Load += FrmMain_Load;
        }
        /// <summary>
        /// 窗体启动加载事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FrmMain_Load(object sender, EventArgs e)
        {

            //配置Serilog 
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.File($"{Application.StartupPath}\\Log\\log.txt", rollingInterval: RollingInterval.Day).CreateLogger();


            StartIni();


            //启动模拟程序
            #region 模拟程序启动
            //实例化一个模拟窗体
            随机模拟.Form1 form1 = new 随机模拟.Form1();
            //设置模拟窗体启动位置
            form1.Location = new Point(this.Location.X, this.Location.Y);
            //设置置顶熟悉
            form1.TopMost = true;
            //显示窗体
            form1.Show();
            #endregion

            //初始化完毕 启动完毕
            isRun = false;
        }

        private void StartIni()
        {
            //读取INI文件的内容
            ReadINI();
            //读取Excel的内容 获取PLC变量相关信息
            ReadExcel();
            //读取PLC
            PLCCommunite();
            //刷新时间控件的timer组件  定时器
            timer1.Start();
            //刷新控件的定时器
            updateTimer.Start();
            //打印Log
            ShowLog("喷涂工艺SCADA系统已成功运行");
            //设置系统初始默认的状态
            this.tce_SysState.Items[0].Text = "设备未连接";
        }



        /// <summary>
        /// 刷新变量值
        /// </summary>
        /// <param name="address"></param>
        /// <param name="value"></param>
        private void UpdateDicVarValue(string address, string value)
        {
            //判断键值对的键是否包含传入的地址
            if (DicVarValue.ContainsKey(address))
            {
                //如果包含且值不一致，就更新值，否则不更新
                if (DicVarValue[address] != value)
                {
                    //更新最新值
                    DicVarValue[address] = value;
                }
            }
            else
            {
                //如果不包含新添加一个
                DicVarValue.Add(address, value);
            }
        }

        /// <summary>
        /// 刷新工位的状态
        /// </summary>
        /// <param name="stationName"></param>
        /// <param name="isOpen"></param>
        private void UpdateDicStationState(string stationName, bool isOpen)
        {
            //如果键值对的键是否包含工位名称
            if (DicStationState.ContainsKey(stationName))
            {
                //如果包含，且状态发生变化，则更新状态值
                if (DicStationState[stationName] != isOpen)
                {
                    //更新
                    DicStationState[stationName] = isOpen;
                }
            }

        }
        /// <summary>
        /// 读取所有的PLC变量
        /// </summary>
        private void ReadExcel()
        {
            try
            {
                //绑定类属性和excel表头名
                dic.Add("VariableName", "变量名称");
                dic.Add("VariableAddress", "变量地址");
                dic.Add("VariableType", "变量类型");
                //读取出来
                variables = ExcelHelper.ExcelToList<Variable>(Application.StartupPath + "\\地址表.xlsx", dic, 0, 0);

                //循环赋值添加到DicVariable里
                for (int i = 0; i < variables.Count; i++)
                {
                    //添加   名称和变量对象对应上  方便后续读取PLC  直接用名称读取
                    DicVariable.Add(variables[i].VariableName, variables[i]);
                }
            }
            catch (Exception ex)
            {
                //弹窗显示报错信息
                MessageBox.Show(ex.ToString());
                //打印Log
                ShowLog(ex.Message, true);
            }

        }

        /// <summary>
        /// PLC读取线程
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        private void PLCCommunite()
        {
            Task task = new Task(() =>
            {

                while (!cts.IsCancellationRequested)
                {
                    if (isConected == false)
                    {
                        int connectResult = nModbusTcp.Connect(ip, port);
                        //连接失败
                        if (connectResult == 2)
                        {
                            //连接标志置为false
                            isConected = false;
                            //设置PLC状态为红灯
                            this.pic_PLClight.BackgroundImage = Properties.Resources.红灯;
                            //打印log
                            ShowLog("PLC连接失败,请查找原因");
                            //跨线程访问控件
                            this.tce_SysState.Invoke(new Action(() =>
                            {
                                this.tce_SysState.Items[0].Text = "设备未连接";
                                this.tce_SysState.Items[0].TextColor = Color.Red;
                            }));
                            //重新连接
                            //等三秒
                            Thread.Sleep(3000);
                        }
                        else
                        {
                            //连接标志置为true
                            isConected = true;
                            //PLC状态设置为绿灯
                            this.pic_PLClight.BackgroundImage = Properties.Resources.绿灯;
                            //打印log
                            ShowLog("PLC连接成功");
                        }
                    }
                    else
                    {
                        //short -32768~32767
                        for (int i = 0; i < variables.Count; i++)
                        {
                            //读取PLC变量
                            string result = nModbusTcp.ReadCommon(DicVariable[variables[i].VariableName]);
                            if (result != null)
                            {
                                //更新字典里的变量值
                                UpdateDicVarValue(variables[i].VariableName, result);
                            }
                            else
                            {
                                //PLC连接失败  重新连接
                                isConected = false;
                                //PLC状态设置为红灯
                                this.pic_PLClight.BackgroundImage = Properties.Resources.红灯;

                            }
                        }
                        //刷新工位的状态
                        isUpdate = true;

                    }
                    //延时100ms
                    Thread.Sleep(100);
                }

            }, cts.Token);
            //启动线程
            task.Start();

        }
        /// <summary>
        /// 读取INI文件的信息
        /// </summary>
        private void ReadINI()
        {
            try
            {
                //读取PLC的IP地址和端口号
                ip = iniFile.IniReadValue("连接PLC参数", "ip");
               
                port = int.Parse(iniFile.IniReadValue("连接PLC参数", "port")
                    );
                //读取工位的启用状态
                string tzStaion = iniFile.IniReadValue("工位启用状态", "脱脂工位");
                //设置控件的状态
                this.sc_TZ.StationState = bool.Parse(tzStaion);
                DicStationState.Add("脱脂工位", bool.Parse(tzStaion));
                //读取工位的启用状态
                string cxStaion = iniFile.IniReadValue("工位启用状态", "粗洗工位");
                //设置控件的状态
                this.sc_CX.StationState = bool.Parse(cxStaion);
                //添加到字典里
                DicStationState.Add("粗洗工位", bool.Parse(cxStaion));

                //读取工位的启用状态
                string thStaion = iniFile.IniReadValue("工位启用状态", "陶化工位");
                //设置控件的状态
                this.sc_TH.StationState = bool.Parse(thStaion);
                //添加到字典里
                DicStationState.Add("陶化工位", bool.Parse(thStaion));
                //读取工位的启用状态
                string jxStaion = iniFile.IniReadValue("工位启用状态", "精洗工位");
                //设置控件的状态
                this.sc_JX.StationState = bool.Parse(jxStaion);
                //添加到字典里
                DicStationState.Add("精洗工位", bool.Parse(jxStaion));
                //读取工位的启用状态
                string sflStaion = iniFile.IniReadValue("工位启用状态", "水分炉工位");
                //设置控件的状态
                this.sc_SFL.StationState = bool.Parse(sflStaion);
                //添加到字典里
                DicStationState.Add("水分炉工位", bool.Parse(sflStaion));
                // 读取工位的启用状态
                string lqStaion = iniFile.IniReadValue("工位启用状态", "冷却室工位");
                //设置控件的状态
                this.sc_LQS.StationState = bool.Parse(lqStaion);
                // 添加到字典里
                DicStationState.Add("冷却室工位", bool.Parse(lqStaion));

                //读取工位的启用状态
                string ghlStaion = iniFile.IniReadValue("工位启用状态", "固化炉工位");
                //设置控件的状态
                this.sc_GHL.StationState = bool.Parse(ghlStaion);
                // 添加到字典里
                DicStationState.Add("固化炉工位", bool.Parse(lqStaion));
                //读取工位的启用状态
                string ssjStaion = iniFile.IniReadValue("工位启用状态", "输送机工位");
                //设置控件的状态
                this.sc_SSJ.StationState = bool.Parse(ssjStaion);
                DicStationState.Add("输送机工位", bool.Parse(ssjStaion));

            }
            catch (Exception ex)
            {
                //弹窗显示报错信息
                MessageBox.Show(ex.ToString());
            }


        }

        /// <summary>
        /// 退出系统
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lbl_Exit_Click(object sender, EventArgs e)
        {
            //关闭程序
            Application.Exit();
        }
      
        /// <summary>
        /// 打印日志和保存日志
        /// </summary>
        /// <param name="str"></param>
        /// <param name="isError"></param>
        private void ShowLog(string str, bool isError = false)
        {
            //如果日志类型为错误
            if (isError)
            {
                //打印错误日志
                Log.Error(str);
            }
            //否则为信息日志
            else
            {
                //打印信息日志
                Log.Information(str);
            }

            //如果需要跨线程打印日志，需要用Invoke方法
            if (this.txt_Info.InvokeRequired)
            {
                this.txt_Info.Invoke(new Action(() =>
                {

                    this.txt_Info.AppendText(DateTime.Now.ToString("HH:mm:ss") + " " + str + Environment.NewLine);
                }));
            }
            else
            {
                this.txt_Info.AppendText(DateTime.Now.ToString("HH:mm:ss") + " " + str + Environment.NewLine);
            }


        }
        #region 控制按钮
        /// <summary>
        /// 启动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_Start_Click(object sender, EventArgs e)
        {
            RisingEdge(DicVariable["总启动"].VariableAddress);
            ShowLog("用户点击了启动按钮");

        }
        /// <summary>
        /// 停止
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_Stop_Click(object sender, EventArgs e)
        {
            RisingEdge(DicVariable["总停止"].VariableAddress);
            ShowLog("用户点击了停止按钮");

        }
        /// <summary>
        /// 复位
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_Reset_Click(object sender, EventArgs e)
        {
            RisingEdge(DicVariable["总复位"].VariableAddress);
            ShowLog("用户点击了复位按钮");

        }
        /// <summary>
        /// 上升沿信号
        /// </summary>
        private async void RisingEdge(ushort address)
        {

            if (isConected)
            {
                nModbusTcp.WriteSingleCoil(address, true);
                //延时1秒
                await Task.Delay(1000);
                nModbusTcp.WriteSingleCoil(address, false);

            }

        }

        #endregion
        /// <summary>
        /// 工位状态变化的事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Common_ChangedEvent(object sender, EventArgs e)
        {
            if (isRun)
            {
                return;
            }
            //获取控件对象
            StationControl stationControl = sender as StationControl;
           //获取工位名称
            iniFile.IniWriteValue("工位启用状态", stationControl.StationName, (stationControl.StationState).ToString());
            //判断状态
            if (stationControl.StationState)
            {
                ShowLog($"{stationControl.StationName}已经启用");

            }
            else
            {
                ShowLog($"{stationControl.StationName}已经关闭");

            }
            //更新字典里的工位状态
            UpdateDicStationState(stationControl.StationName, stationControl.StationState);
        }

        /// <summary>
        /// 定时器刷新时间
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer1_Tick(object sender, EventArgs e)
        {
            //刷新时间控件
            this.nte_time.Value = DateTime.Now;



        }
        /// <summary>
        /// 刷新控件的定时器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void updateTimer_Tick(object sender, EventArgs e)
        {
            try
            {
                //如果PLC连接成功并且可以刷新
                if (isConected && isUpdate)
                {
                    //根据绑定的tag值获取变量名，然后根据变量名获取最新值
                    //刷新状态
                    if (bool.Parse(DicVarValue[tce_SysState.Tag.ToString()]))
                    {
                        tce_SysState.Items[0].Text = "系统正在报警";
                        tce_SysState.Items[0].TextColor = Color.Red;
                    }
                    else
                    {
                        tce_SysState.Items[0].Text = "系统正常运行";
                        tce_SysState.Items[0].TextColor = Color.Green;
                    }
                    
                    tp1.Value = DicVarValue[tp1.Tag.ToString()].ToString();
                    tp2.Value = DicVarValue[tp2.Tag.ToString()].ToString();
                    tp3.Value = DicVarValue[tp3.Tag.ToString()].ToString();
                    wp1.Value = float.Parse(float.Parse(DicVarValue[wp1.Tag.ToString()]).ToString("f2"));
                    wp2.Value = float.Parse(float.Parse(DicVarValue[wp2.Tag.ToString()]).ToString("f2"));
                    wp3.Value = float.Parse(float.Parse(DicVarValue[wp3.Tag.ToString()]).ToString("f2"));
                    pp1.Value = float.Parse(float.Parse(DicVarValue[pp1.Tag.ToString()]).ToString("f2"));
                    mbe1.Value = float.Parse(float.Parse(DicVarValue[mbe1.Tag.ToString()]).ToString("f2"));
                    mbe2.Value = float.Parse(float.Parse(DicVarValue[mbe2.Tag.ToString()]).ToString("f2"));

                    //遍历容器中的控件
                    foreach (var item in this.gp2.Controls)
                    {
                        //判断是否是设备状态控件
                        if (item is DeviceState deviceState)
                        {
                            //根据tag值获取变量名，然后根据变量名获取最新值
                            deviceState.State = bool.Parse(DicVarValue[deviceState.Tag.ToString()].ToString());
                        }
                        else if (item is DeviceAlarm deviceAlarm)
                        {
                            deviceAlarm.State = bool.Parse(DicVarValue[deviceAlarm.Tag.ToString()].ToString());
                        }

                    }

                    //遍历底部面板中的控件
                    foreach (var item in this.bottomPanel.Controls)
                    {
                        if (item is StationState stationState)
                        {
                            //如果工位开启
                            if (DicStationState[stationState.DeviceName])
                            {
                                bool state = bool.Parse(DicVarValue[stationState.Tag.ToString()].ToString());
                                if (state)
                                {
                                    //true报警
                                    stationState.State = 2;
                                }
                                else
                                {
                                    //false正常
                                    stationState.State = 1;
                                }
                            }
                            else
                            {
                                //未启用
                                stationState.State = 3;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {

                MessageBox.Show(ex.ToString());
                ShowLog(ex.Message, true);
            }

        }

        #region 无边框拖动

        private Point mPoint;
        /// <summary>
        /// 鼠标按下事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Panel_MouseDown(object sender, MouseEventArgs e)
        {
            mPoint = new Point(e.X, e.Y);
        }
        /// <summary>
        /// 鼠标移动事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Panel_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                this.Location = new Point(this.Location.X + e.X - mPoint.X, this.Location.Y + e.Y - mPoint.Y);
            }
        }

        #endregion

        #region 窗体一次性加载，不闪烁
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams paras = base.CreateParams;
                paras.ExStyle |= 0x02000000;
                return paras;
            }
        }
        #endregion
    }

}
